# -*- coding: utf-8 -*-

"""
Created on Fri Jun  8 18:43:24 2018

@author: Administrator
"""
import os
import glob
import sys
from ..log import Log_make
from ..distribute import MultiThread
print('spine--spine--spine')

#%%
class Miscellany(object):
    @staticmethod
    def get_prefix(key):
        #作用原理：将key='delete_firstN_volumes_dfv'中的'dfv'部分提取出来
        #参数说明:key是manipulate_methods.keys()的元素
        #返回值说明：返回一个字符串
        #举例：
        #调用：无
        #被调用：self.dirpath_dict_assign
        #bug：
        underline = '_'
        elements = key.split(underline)
        length = len(key.split(underline))
        prefix = elements[length-1]
        return prefix

    def permutate_2nest_list(self,nest_list,numb=-1,combine_str_list=[],medi_list=[],easy_list=[]):
        #作用原理：将一个双层嵌套的list(nest_list)）内的个子list的的元素排列组合,并返回。
        #参数说明：nest_list是一个双层嵌套的list
        #参数说明：numb用来作为nest_list的元素的索引   combine_str_list存放排列组合的半成品，
        #参数说明：medi_list辅助conbine_str_list,easy_list是最后用于返回的成品。
        #返回值说明：返回值即为easy_list
        #举例：nest = [['sss','ttt','yyy'],[30,20],['aa','bb']]  返回的eash_list = ['-sss-30-aa','ttt-30-aa'...]
        #调用：无
        #被调用：dirpath_dict_assign
        #bug：将来要把‘-’改成‘_’
        link_line = '-'
        numb = numb + 1
        print('a new iteration----------------------------')
        print('numb:',numb)
        print('the type of the easy_list:',type(easy_list))

        if len(combine_str_list) != 0:
            print('in the first if')
            print('numb:',numb)
            for item in nest_list[numb]:
                print('item:',item)
                print('combine_str_list:',combine_str_list)
                medi_list.extend([elem + link_line + str(item)  for elem in combine_str_list])
                print('medi_list:',medi_list)
            combine_str_list = medi_list
            print('combine_str_list:',combine_str_list)
            if numb == len(nest_list) - 1:
                return combine_str_list 
            easy_list = self.permutate_2nest_list(nest_list,numb,combine_str_list,medi_list=[],easy_list=[])
            return easy_list
        if len(combine_str_list) == 0:
            print('in the second if')
            print('numb:',numb)
            combine_str_list = [link_line + str(item) for item in nest_list[numb]]
            print('combine_str_list:',combine_str_list)
            if len(nest_list) == 1:
                return combine_str_list
            easy_list = self.permutate_2nest_list(nest_list,numb,combine_str_list,medi_list=[],easy_list=[])
            return easy_list

    def dirpath_dict_assign(self,dirpath_dict,manipulate_methods):
        #作用原理：利用manipulate_methods生成对应的dirpath
        #参数说明：
        #参数说明：
        #返回值说明：
        #举例：
        #调用：permutate_2nest_list
        #被调用：
        #bug：将来要把‘_’改成‘-’
        underline = '_'
        i = 1
        for key in dirpath_dict.keys():
            if i == 1:
                #because the 1st elements of dirpath_dict is 'original_dirpath' which should be not be preocessed as other elements.
                i = i + 1
                continue
            else: 
                last_key = list(dirpath_dict.keys())[i-2]
                print('last_key:',last_key)
                prefix = self.get_prefix(key)
                print('prefix:',prefix)
                if len(manipulate_methods[key]) == 0:
                    dirpath_dict[key].extend( last_dirpath + underline + prefix for last_dirpath in dirpath_dict[last_key] ) 
                else:
                    paramter_permutation = self.permutate_2nest_list(list(manipulate_methods[key].values()))
                    for parameter in paramter_permutation:
                        dirpath_dict[key].extend( last_dirpath + underline + prefix + str(parameter) for last_dirpath in dirpath_dict[last_key] )
            i = i + 1

        return dirpath_dict

    @staticmethod
    def present_filename_list_montage(item,original_filepath_list,processed_info):
#        print('in the present_filename_list_montage')
        dot = '.'
        suffix_nii = r'.nii'
        suffix_nii_gz = r'.nii.gz'
        present_filename_list = []
        for original_filepath in original_filepath_list:
            print('original_filepath:',original_filepath)
            suj_dir_name =  os.path.split(original_filepath)[1].split(suffix_nii)[0]
            #下面if elif的条件的顺序是固定的。因为.nii也会在.nii.gz的文件中。
            if suffix_nii_gz in original_filepath:
                present_filename_list.append(item + os.sep + suj_dir_name + processed_info + suffix_nii_gz)
                #公式说明：processed_info是指已经完成的处理过程产生的信息如'_DFV-4_DS-7_ST-sss-30-aa'
            elif suffix_nii in original_filepath:
                present_filename_list.append(item + os.sep + suj_dir_name + processed_info + suffix_nii)
            else :
                raise Exception("the input file format is not .nii or nii.gz")         
        return present_filename_list
    @staticmethod
    def get_input_info(present_filename,processed_info):
        #作用原理：通过文件路径present_filename获得来源文件previous_filename,以及处理所需的参数parameter_list 
        #参数说明：processed_info在本函数中并没有被用到。因为present_filename中的信息已经足够多，但之所以没有删除processed_info这个参数，是因为太懒了。
        #参数说明：present_filename = r'/media/root/Elements/XuanWu_gz/T1_CP/subj_240_1_T1_CP.nii.gz'
        #返回值说明：
        #举例：
        #调用：
        #被调用：agile_pipe
        #bug：这个函数才开始是按照.nii文件写的，后来为了兼容.nii.gz格式，故在之前的基础上update了一下，但
        underline = '_'
        link_line = '-'
        suffiix_gz = '.nii.gz'
        #下面主要是为了获得input所需的文件previous_filename，分别当文件是.nii.gz格式或.nii格式时。注意，只能先判断是不是.nii.gz格式.
        if suffiix_gz in present_filename:
            present_filename_gz = present_filename
            present_filename = os.path.splitext(present_filename_gz)[0]

            present_fn_no_ext = os.path.splitext(present_filename)[0]
            processing_info = present_fn_no_ext.split('_')[len(present_fn_no_ext.split('_'))-1]
            medi = underline + processing_info
            #processing_info是即将要进行的处理所需要的参数信息，要区别主函数agile_pipe中的processed_info
            previous_filename = present_filename_gz.replace(medi,'')
        else:
            #当时.nii文件格式时
            present_fn_no_ext = os.path.splitext(present_filename)[0]
            processing_info = present_fn_no_ext.split('_')[len(present_fn_no_ext.split('_'))-1]
            medi = underline + processing_info
            #processing_info是即将要进行的处理所需要的参数信息，要区别主函数agile_pipe中的processed_info
            previous_filename = present_filename.replace(medi,'')

        #下面的主要是为了获得input所需的文件parameter_list     
        elems = processing_info.split(link_line)
        #若processing_info中没有link-line,则elems=[''],则下面的parameter_list = []
        parameter_list = elems[1:len(elems)]
        return previous_filename,parameter_list 


# In[ ]:


class AgilePipe(Miscellany,MultiThread,Log_make):
    #作用原理：
    #参数说明：original_dirpath是指存放多个subject的bold（或other modality） img文件的位置。
    #参数说明：raw_t1_dirpath,self.dirpath_dict是指存放多个subject的T1像 img文件的位置。因为T1像的通常作为空间模板来使用。
    #参数说明：self.dirpath_dict是一个有固定格式的dict，用于存放处理过程中所有需要生成的文件夹的完整路径。
    #参数说明：manipulate_methods是一个有固定格式的dict，键值为处理的函数名，元素为对应处理函数所需要的各种参数
    #参数说明：core_percent表示用百分之多少的cpu核心来并行处理数据。只能取非0数。
    #返回值说明：
    #举例：
    #调用：
    #被调用：
    #bug：
    
    #主要逻辑结构
    def __init__(self,modality,original_dirpath,dirpath_list,dirpath_dict,manipulate_methods,core_percent):
        print('original_dirpath:',original_dirpath)
        original_filepath_list = glob.glob(original_dirpath + os.sep + r'*')
#        print('original_filepath_list:',original_filepath_list)
        #参数准备：
        self.dirpath_dict = dirpath_dict
        self.dirpath_dict['original_dirpath'].append(original_dirpath)
        self.dirpath_dict = self.dirpath_dict_assign(self.dirpath_dict,manipulate_methods)
        #self.dirpath_dict_assign()此函数的作用是生成每一步处理之后存放新数据的文件夹。以备后边的程序使用。
        print('dirpath_dict:',self.dirpath_dict)
        present_filename_list = []
        
        for key in manipulate_methods.keys():
            #manipulate_methods.keys()中的元素(字符串)是正好是需要调用以处理数据的函数名，如'delete_firstN_volumes_DFV'对应函数delete_firstN_volumes_DFV()
            #这层循环的目的是为了按照顺序执行manipulate_methods这个dict中的数据处理步骤。
            for item in self.dirpath_dict[key]:
                #这一次循环中是利用self.dirpath_dict与manipulate_methods用于共同的key，self.dirpath_dict[key]包含则是这一步完成之后存放新生成数据的文件夹路径。
                #路劲的生成是由于是各种参数的排列组合，故此处是一个list，用item逐个取出此list的元素。构造present_filename_list这个变量，然后以此为基础进行for循环。
                print('item:',item)
                processing_parameter_list = []
                #this is essential because list is a muttable,so it should be initialized in each 'for' cycle.

                if not os.path.exists(item):
                    os.makedirs(item)

                #processed_info = item.replace(original_dirpath,'') 
                processed_info = item.replace(original_dirpath,'')
                #processed_info是指已经完成的处理过程产生的信息如'_DFV-4_DS-7_ST-sss-30-aa'，有两个作用：1.用于构造 present_filename_list，此时我们需要其全部信息。
                #2.用于构造cmd_flag_parameter_list(也可以不用)，如果用我们需要的仅仅是从ST'-sss-30-aa'这一部分，详情见后边的get_input_info()函数。
                print('processed_info:',processed_info)

                present_filename_list = self.present_filename_list_montage(item,original_filepath_list,processed_info)
                print('present_filename_list:',present_filename_list)
                print('numb_of_present_filename_list:',len(present_filename_list))

                
                #这是为self.parallel构建processing_parameter_list
                for present_filename in present_filename_list :
                    #这一层循环的目的是生成processing_parameter_list，以某一个经过处理后的的.nii文件是否存在拍判断，若存在，则不把
                    #子此文件名加入到processing_parameter_list，进而避免重复运算。若不存在，则加入。
                    if os.path.exists(present_filename) or os.path.islink(present_filename):
                        continue
                    else:
                        #print('present_filename:',present_filename)
                        previous_filename,cmd_flag_parameter_list = self.get_input_info(present_filename,processed_info)
                        #cmd_flag_dict与这里生成的cmd_flag_parameter_list一一组合之后，共同构成afni数据处理的脚本。
                        cmd_flag_dict = manipulate_methods[key]
                        #此变量用于构造完整的afni数据处理命令，但截至至2018.04.17日还为实现。可参靠ppt的说明。
                        #print('previous_filename:',previous_filename)
                        #print('cmd_flag_parameter_list:',cmd_flag_parameter_list)
                        processing_parameter_list.append([previous_filename,cmd_flag_dict,cmd_flag_parameter_list,present_filename,dirpath_list,modality])
                print('processing_parameter_list:',processing_parameter_list)     
                
                
                #下面的部分是实现parallel processing的代码.
                process_result_list = self.parallel(key,processing_parameter_list,core_percent)
                #print('length of process_result_list:',len(process_result_list))
                #print('process_result_list:',process_result_list)
                
                
                #下面的代码是根据self.parallel返回的结果进行日志记录
                i = 0
                for process_obj in process_result_list:
                    if process_obj.stdout_err != None:
                        #之所以这样判断是因为afni下面的如3dcalc等命令的stdout stderr非常特殊。详见key值指向的函数。
                        print('errorerrorerrorerrorerrorerrorerror')
                        print('stdout_err:',process_obj)
                        print('i:',i)
                        if not os.path.exists(item):
                            os.makedirs(item)
                        err_log_path = item + os.sep + sys._getframe().f_code.co_name + '_err_log.txt'
                        self.shell_err_log_write(err_log_path,key,process_obj.stdout_err,item,i)
                        #previous_filename,present_filename这两个参数是不对的。
                        i = i + 1